/*
 * Copyright 2012 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid.cell;

import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.PopupPanel;
import finbudgettable.client.ui.grid.event.ValueChangedEvent;
import finbudgettable.client.ui.grid.event.ValueChangedEventHandler;

public abstract class AbstractPopupCell<C, E extends AbstractPopupEditor<C>> extends AbstractEditableGridCell<C, C> {

    private static final int KEY_F2 = 113;
    private Object lastKey;
    private Element lastParent;
    private int lastIndex;
    private int lastColumn;
    private C lastValue;
    private ValueUpdater<C> valueUpdater;
    protected final E editor;

    public AbstractPopupCell(E editor) {
        super(BrowserEvents.CLICK, BrowserEvents.KEYPRESS, BrowserEvents.KEYDOWN, BrowserEvents.BLUR);
        this.editor = editor;
        editor.addCloseHandler(closeHandler);
        editor.addHandler(valueChangedHandler, ValueChangedEvent.TYPE);
    }

    private final CloseHandler<PopupPanel> closeHandler = new CloseHandler<PopupPanel>() {
        public void onClose(CloseEvent<PopupPanel> event) {
            lastKey = null;
            lastValue = null;
            lastIndex = -1;
            lastColumn = -1;
            if (lastParent != null && !event.isAutoClosed()) {
                lastParent.focus();
                Scheduler.get().scheduleFinally(
                        new Scheduler.ScheduledCommand() {
                            Element cell = lastParent;

                            @Override
                            public void execute() {
                                // A fake event to ensure the grid to mark the cell as not editing
                                cell.dispatchEvent(Document.get().createKeyDownEvent(true, true, true, true, 0));
                            }
                        });
            }
            lastParent = null;
        }
    };

    private final ValueChangedEventHandler<C> valueChangedHandler = new ValueChangedEventHandler<C>() {
        @Override
        public void onValueChanged(ValueChangedEvent<C> event) {
            // Remember the values before hiding the popup.
            Element cellParent = lastParent;
//            C oldValue = lastValue;
            Object key = lastKey;
            int index = lastIndex;
            int column = lastColumn;
            //editor.hide();

            // Update the cell and value updater.
            C value = event.getValue();
            setViewData(key, value);
            if (cellParent != null) {
                setValue(new Context(index, column, key), cellParent, value);
            }
            if (valueUpdater != null) {
                valueUpdater.update(value);
            }
        }
    };

    @Override
    public boolean isEditing(Context context, Element parent, C value) {
        return lastKey != null && lastKey.equals(context.getKey());
    }

    @Override
    public void render(Context context, C value, SafeHtmlBuilder sb) {
        Object key = context.getKey();
        C viewData = getViewData(key);
        if (viewData != null && viewData.equals(value)) {
            clearViewData(key);
            viewData = null;
        }
        render(context, value, viewData, sb);
    }

    protected abstract void render(Context context, C value, C viewData, SafeHtmlBuilder sb);

    @Override
    public void onBrowserEvent(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> valueUpdater) {
        super.onBrowserEvent(context, parent, value, event, valueUpdater);
        String type = event.getType();
        if (BrowserEvents.CLICK.equals(type) || (BrowserEvents.KEYDOWN.equals(type) && event.getKeyCode() == KEY_F2)) {
            startEdit(context, parent, value, event, valueUpdater);
        }
    }

    @Override
    protected void onEnterKeyDown(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> stringValueUpdater) {
        startEdit(context, parent, value, event, stringValueUpdater);
    }

    public void startEdit(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> valueUpdater) {
        this.lastKey = context.getKey();
        this.lastParent = parent;
        this.lastValue = value;
        this.lastIndex = context.getIndex();
        this.lastColumn = context.getColumn();
        this.valueUpdater = valueUpdater;

        C viewData = getViewData(lastKey);
        C editValue = (viewData == null) ? lastValue : viewData;

        editor.setValue(editValue);
        editor.setParent(lastParent);
        editor.setReadOnly(isReadonly());
        popup(editor);
    }

    protected abstract void popup(E editor);

    protected Element getLastParent() {
        return lastParent;
    }
//
//    protected boolean canShowEditor() {
//        return !isReadonly();
//    }

    protected C getLastValue() {
        return lastValue;
    }
}
